import sys
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, 
                            QHBoxLayout, QTableWidget, QTableWidgetItem, QPushButton, 
                            QLabel, QTextEdit, QHeaderView, QInputDialog, QMessageBox)
from PyQt6.QtCore import QTimer, Qt, pyqtSignal, QObject
from PyQt6.QtGui import QColor
from bleak import BleakScanner, BleakClient
from datetime import datetime
import asyncio
import threading
import qasync
from functools import partial
from bluetooth_scanner import BluetoothRadar

# 在文件开头添加服务UUID字典
BLUETOOTH_SERVICES = {
    # 通用服务
    "1800": "通用访问配置服务 (Generic Access)",
    "1801": "通用属性配置服务 (Generic Attribute)",
    "1802": "立即提醒服务 (Immediate Alert)",
    "1803": "链路丢失服务 (Link Loss)",
    "1804": "发射功率服务 (Tx Power)",
    "1805": "当前时间服务 (Current Time)",
    "1806": "参考时间更新服务 (Reference Time Update)",
    "1807": "下次DST变更服务 (Next DST Change)",
    "1808": "葡萄糖服务 (Glucose)",
    "1809": "健康温度计服务 (Health Thermometer)",
    "180A": "设备信息服务 (Device Information)",
    "180D": "心率服务 (Heart Rate)",
    "180E": "电话提醒服务 (Phone Alert Status)",
    "180F": "电池服务 (Battery Service)",
    "1810": "血压服务 (Blood Pressure)",
    "1811": "警报通知服务 (Alert Notification)",
    "1812": "人机接口设备服务 (Human Interface Device)",
    "1813": "扫描参数服务 (Scan Parameters)",
    "1814": "跑步传感器服务 (Running Speed and Cadence)",
    "1815": "自动化IO服务 (Automation IO)",
    "1816": "骑行速度与节奏服务 (Cycling Speed and Cadence)",
    "1818": "骑行功率服务 (Cycling Power)",
    "1819": "位置导航服务 (Location and Navigation)",
    
    # 苹果专有服务
    "FEE7": "Apple 通知中心服务 (Notification Center)",
    "FEE0": "Apple 媒体服务 (Media)",
    "FEF5": "Apple 音频服务 (Audio)",
    
    # 小米专有服务
    "FE95": "小米可穿戴服务 (Mi Wearable)",
    
    # 其他常见服务
    "FFF0": "设备控制服务 (Device Control)",
    "FFE0": "透传服务 (Transparent Communication)",
}

class BluetoothSignals(QObject):
    device_updated = pyqtSignal(object)
    scan_error = pyqtSignal(str)
    connection_success = pyqtSignal(str)

class BluetoothGUI(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("蓝牙设备扫描器")
        self.setGeometry(100, 100, 1200, 800)
        
        # 创建信号对象
        self.signals = BluetoothSignals()
        self.signals.device_updated.connect(self.update_device_info)
        self.signals.scan_error.connect(self.show_error)
        self.signals.connection_success.connect(self.handle_connection_success)
        
        # 存储设备数据
        self.devices = {}
        self.scanner = None
        self.is_scanning = False
        
        # 添加扫描计时器
        self.scan_timer = None
        self.scan_interval = 5  # 扫描间隔（秒）
        
        # 存储UI组件的引用
        self.right_panel = None
        self.right_layout = None
        self.detail_text = None
        self.current_buttons = []  # 存储当前显示的按钮
        
        self.init_ui()
    
    def init_ui(self):
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        main_layout = QHBoxLayout(central_widget)
        
        # 左侧设备列表
        left_panel = QWidget()
        left_layout = QVBoxLayout(left_panel)
        
        # 设备表格
        self.device_table = QTableWidget()
        self.device_table.setColumnCount(4)
        self.device_table.setHorizontalHeaderLabels(["设备地址", "名称", "信号强度", "最后更新"])
        self.device_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
        self.device_table.itemClicked.connect(self.show_device_details)
        left_layout.addWidget(self.device_table)
        
        # 控制按钮
        button_layout = QHBoxLayout()
        self.scan_button = QPushButton("开始扫描")
        self.scan_button.clicked.connect(self.handle_scan_click)
        self.clear_button = QPushButton("清除列表")
        self.clear_button.clicked.connect(self.clear_devices)
        button_layout.addWidget(self.scan_button)
        button_layout.addWidget(self.clear_button)
        left_layout.addLayout(button_layout)
        
        # 添加雷达视图
        self.radar_view = BluetoothRadar()
        left_layout.addWidget(self.radar_view)
        
        # 右侧详细信息面板
        self.right_panel = QWidget()
        self.right_layout = QVBoxLayout(self.right_panel)
        
        self.right_layout.addWidget(QLabel("设备详细信息"))
        self.detail_text = QTextEdit()
        self.detail_text.setReadOnly(True)
        self.right_layout.addWidget(self.detail_text)
        
        main_layout.addWidget(left_panel, 2)
        main_layout.addWidget(self.right_panel, 1)
    
    def clear_button_layout(self):
        """清理按钮布局"""
        # 清除所有已存储的按钮
        for button in self.current_buttons:
            button.deleteLater()
        self.current_buttons.clear()

        # 移除旧的布局
        for i in reversed(range(self.right_layout.count())):
            item = self.right_layout.itemAt(i)
            if isinstance(item, QHBoxLayout):
                while item.count():
                    widget = item.takeAt(0).widget()
                    if widget:
                        widget.deleteLater()
                self.right_layout.removeItem(item)
    
    async def start_scanning(self):
        """启动蓝牙扫描"""
        if not self.is_scanning:
            self.is_scanning = True
            self.scan_button.setText("停止扫描")
            
            while self.is_scanning:
                try:
                    # 开始扫描
                    self.scanner = BleakScanner()
                    self.scanner.register_detection_callback(self.detection_callback)
                    await self.scanner.start()
                    
                    # 扫描5秒
                    await asyncio.sleep(5)
                    
                    # 停止当前扫描
                    if self.scanner:
                        await self.scanner.stop()
                        self.scanner = None
                    
                    # 如果还在扫描模式，等待间隔时间
                    if self.is_scanning:
                        await asyncio.sleep(self.scan_interval)
                except Exception as e:
                    self.detail_text.append(f"扫描错误: {str(e)}")
                    self.is_scanning = False
                    self.scan_button.setText("开始扫描")
                    break

    async def stop_scanning(self):
        """停止蓝牙扫描"""
        if self.is_scanning:
            self.is_scanning = False
            self.scan_button.setText("开始扫描")
            if self.scanner:
                try:
                    await self.scanner.stop()
                except Exception as e:
                    self.detail_text.append(f"停止扫描错误: {str(e)}")
                finally:
                    self.scanner = None

    def detection_callback(self, device, advertisement_data):
        """蓝牙设备检测回调"""
        device_data = {
            'address': device.address,
            'name': device.name or advertisement_data.local_name or "未连接",
            'rssi': advertisement_data.rssi,
            'last_seen': datetime.now(),
            'advertisement_data': advertisement_data,
            'details': str(device)
        }
        self.signals.device_updated.emit(device_data)

    async def toggle_scanning(self):
        """切换扫描状态"""
        try:
            if self.is_scanning:
                await self.stop_scanning()
            else:
                await self.start_scanning()
        except Exception as e:
            self.detail_text.append(f"切换扫描状态错误: {str(e)}")
            self.is_scanning = False
            self.scan_button.setText("开始扫描")
            if self.scanner:
                self.scanner = None

    def update_device_info(self, device_data):
        """更新设备信息"""
        address = device_data['address']
        self.devices[address] = device_data
        
        # 检查设备过期
        self.check_device_expiry()
        
        # 更新UI
        self.update_device_table()
        self.radar_view.update_device_position(
            address,
            device_data['name'],
            device_data['rssi']
        )
    
    def update_device_table(self):
        self.device_table.setRowCount(len(self.devices))
        for row, (address, device) in enumerate(self.devices.items()):
            self.device_table.setItem(row, 0, QTableWidgetItem(address))
            self.device_table.setItem(row, 1, QTableWidgetItem(device['name']))
            self.device_table.setItem(row, 2, QTableWidgetItem(f"{device['rssi']} dBm"))
            
            # 添加连接状态显示
            status = "已连接" if device.get('connected', False) else "未连接"
            self.device_table.setItem(row, 3, QTableWidgetItem(
                f"{device['last_seen'].strftime('%H:%M:%S')} ({status})"))
            
            self.color_row_by_rssi(row, device['rssi'])
    
    def color_row_by_rssi(self, row, rssi):
        if rssi >= -35:
            color = QColor(144, 238, 144)  # 浅绿色
        elif rssi >= -70:
            color = QColor(255, 255, 224)  # 浅黄色
        else:
            color = QColor(255, 228, 225)  # 浅红色
            
        for col in range(self.device_table.columnCount()):
            item = self.device_table.item(row, col)
            if item:
                item.setBackground(color)
    
    def show_device_details(self, item):
        row = item.row()
        address = self.device_table.item(row, 0).text()
        device = self.devices.get(address)
        
        if device:
            # 清理旧的按钮
            self.clear_button_layout()
            
            # 添加新的连接按钮
            connect_button = QPushButton("连接设备")
            connect_button.clicked.connect(
                lambda: self.handle_connect_click(address))
            self.current_buttons.append(connect_button)
            
            adv_data = device['advertisement_data']
            details = f"""设备详细信息:
地址: {device['address']}
名称: {device['name']}
信号强度: {device['rssi']} dBm
最后发现时间: {device['last_seen'].strftime('%Y-%m-%d %H:%M:%S')}

广播数据:
{self.format_advertisement_data(adv_data)}

原始详情:
{device['details']}
"""
            self.detail_text.setText(details)
            
            # 创建新的按钮布局
            button_layout = QHBoxLayout()
            button_layout.addWidget(connect_button)
            button_layout.addStretch()
            
            # 添加按钮布局
            self.right_layout.insertLayout(1, button_layout)
    
    def format_advertisement_data(self, adv_data):
        formatted = []
        
        # 处理制造商数据
        if adv_data.manufacturer_data:
            formatted.append("制造商数据:")
            for company_code, data in adv_data.manufacturer_data.items():
                company_name = self.get_company_name(company_code)
                formatted.append(f"  制造商: {company_name} (0x{company_code:04x})")
                formatted.append(f"  原始数据: {data.hex()}")
                formatted.append(self.analyze_manufacturer_data(company_code, data))
        
        # 处理服务数据
        if adv_data.service_data:
            formatted.append("\n服务数据:")
            for uuid, data in adv_data.service_data.items():
                service_name = self.get_service_name(uuid)
                formatted.append(f"  服务: {service_name}")
                formatted.append(f"  UUID: {uuid}")
                formatted.append(f"  数据: {data.hex()}")
                formatted.append(self.analyze_service_data(uuid, data))
        
        # 处理服务UUID
        if adv_data.service_uuids:
            formatted.append("\n支持的服务:")
            for uuid in adv_data.service_uuids:
                service_name = self.get_service_name(uuid)
                formatted.append(f"  - {service_name}")
                formatted.append(f"    UUID: {uuid}")
        
        return "\n".join(formatted) if formatted else "无广播数据"
    
    def get_service_name(self, uuid):
        """解析服务UUID"""
        uuid = uuid.upper()
        # 提取UUID的关键部分（前4位）
        key = uuid.split('-')[0][:4] if '-' in uuid else uuid[:4]
        return f"{BLUETOOTH_SERVICES.get(key, '未知服务')} ({key})"

    def get_company_name(self, company_code):
        """获取制造商名称"""
        # 这里可以添加更多公司代码
        COMPANIES = {
            0x004C: "Apple Inc.",
            0x038F: "小米科技",
            0x0075: "三星电子",
            0x0059: "Nordic Semiconductor",
            0x0001: "Ericsson Technology",
            0x000A: "华为技术",
            0x000F: "Broadcom Corporation",
            0x0157: "OPPO广东移动通信",
            0x0499: "VIVO移动通信",
        }
        return COMPANIES.get(company_code, "未知制造商")

    def analyze_manufacturer_data(self, company_code, data):
        """分析制造商特定数据"""
        result = []
        try:
            if company_code == 0x004C:  # Apple
                result.append("  Apple iBeacon数据:")
                if len(data) >= 23:
                    beacon_type = data[0:2].hex()
                    uuid = data[2:18].hex()
                    major = int.from_bytes(data[18:20], byteorder='big')
                    minor = int.from_bytes(data[20:22], byteorder='big')
                    power = data[22]
                    result.append(f"    类型: {beacon_type}")
                    result.append(f"    UUID: {uuid}")
                    result.append(f"    Major: {major}")
                    result.append(f"    Minor: {minor}")
                    result.append(f"    发射功率: {power}dBm")
            
            elif company_code == 0x038F:  # 小米
                result.append("  小米设备数据:")
                if len(data) >= 5:
                    frame_control = int.from_bytes(data[0:2], byteorder='little')
                    product_id = int.from_bytes(data[2:4], byteorder='little')
                    frame_counter = data[4]
                    result.append(f"    帧控制: 0x{frame_control:04x}")
                    result.append(f"    产品ID: {product_id}")
                    result.append(f"    帧计数: {frame_counter}")
        except Exception as e:
            result.append(f"  数据解析错误: {str(e)}")
        
        return "\n".join(result) if result else "  无法解析的制造商数据"

    def analyze_service_data(self, uuid, data):
        """分析服务数据"""
        result = []
        try:
            if uuid.startswith("180F"):  # 电池服务
                if len(data) >= 1:
                    battery_level = data[0]
                    result.append(f"  电池电量: {battery_level}%")
            
            elif uuid.startswith("180D"):  # 心率服务
                if len(data) >= 2:
                    flags = data[0]
                    heart_rate = data[1]
                    result.append(f"  心率值: {heart_rate} BPM")
        except Exception as e:
            result.append(f"  数据解析错误: {str(e)}")
        
        return "\n".join(result) if result else "  无法解析的服务数据"
    
    def clear_devices(self):
        # 清除所有设备
        for address in list(self.devices.keys()):
            self.radar_view.remove_device(address)
        self.devices.clear()
        self.device_table.setRowCount(0)
        self.detail_text.clear()
    
    def show_error(self, error_msg):
        self.detail_text.setText(f"错误: {error_msg}")
    
    def closeEvent(self, event):
        """窗口关闭时的处理"""
        if self.scanner:
            loop = asyncio.get_event_loop()
            if loop and loop.is_running():
                loop.create_task(self.stop_scanning())
        event.accept()

    async def connect_to_device(self, address):
        """连接到指定地址的设备"""
        try:
            self.detail_text.append(f"\n正在连接到设备 {address}...")
            async with BleakClient(address) as client:
                if client.is_connected:
                    self.detail_text.append("连接成功！")
                    
                    # 发送连接成功信号
                    self.signals.connection_success.emit(address)
                    
                    # 尝试发送连接成功通知到设备
                    try:
                        # 查找通知特征值
                        services = await client.get_services()
                        notification_sent = False
                        
                        # 首先尝试查找标准的通知服务
                        standard_service_uuids = [
                            "1811",  # Alert Notification Service
                            "1815",  # Automation IO
                            "FFE0",  # 透传服务
                        ]
                        
                        for service in services:
                            service_uuid = service.uuid.upper()
                            service_key = service_uuid.split('-')[0][:4]
                            
                            if service_key in standard_service_uuids:
                                for char in service.characteristics:
                                    if ("write" in char.properties or 
                                        "write-without-response" in char.properties):
                                        try:
                                            # 发送连接成功通知
                                            notification_data = bytes([0x01, 0x00])
                                            response = "write" in char.properties
                                            await client.write_gatt_char(
                                                char.uuid, 
                                                notification_data, 
                                                response=response
                                            )
                                            self.detail_text.append(
                                                f"已发送连接成功通知到特征值: {char.uuid}"
                                            )
                                            notification_sent = True
                                            break
                                        except Exception as write_error:
                                            continue
                            
                            if notification_sent:
                                break
                        
                        if not notification_sent:
                            self.detail_text.append("未找到合适的特征值发送连接通知")
                            
                    except Exception as service_error:
                        self.detail_text.append(f"获取服务失败: {str(service_error)}")

                    # 显示设备服务和特征值
                    services = await client.get_services()
                    self.detail_text.append("\n发现的服务:")
                    
                    # 清理旧的按钮
                    self.clear_button_layout()
                    
                    # 创建新的交互按钮布局
                    interaction_layout = QHBoxLayout()
                    
                    for service in services:
                        service_name = self.get_service_name(service.uuid)
                        self.detail_text.append(f"\n服务: {service_name}")
                        self.detail_text.append(f"UUID: {service.uuid}")
                        
                        # 获取服务的特征值
                        for char in service.characteristics:
                            char_uuid = char.uuid  # 保存特征值UUID
                            self.detail_text.append(f"\n  特征值: {char_uuid}")
                            self.detail_text.append(f"  属性: {self.get_characteristic_properties(char)}")
                            
                            # 为可读特征值创建读取按钮
                            if "read" in char.properties:
                                read_button = QPushButton(f"读取 {service_name}")
                                # 使用 partial 来正确绑定参数
                                read_button.clicked.connect(
                                    lambda checked, c=char_uuid: self.handle_read_click(client, c))
                                self.current_buttons.append(read_button)
                                interaction_layout.addWidget(read_button)
                            
                            # 可写特征值创建写入按钮
                            if "write" in char.properties or "write-without-response" in char.properties:
                                write_button = QPushButton(f"写入 {service_name}")
                                write_button.clicked.connect(
                                    lambda checked, c=char_uuid, p=char.properties: 
                                    self.handle_write_click(client, c, p))
                                self.current_buttons.append(write_button)
                                interaction_layout.addWidget(write_button)
                            
                            # 为持通知的特征值创建订阅按钮
                            if "notify" in char.properties:
                                notify_button = QPushButton(f"订阅 {service_name}")
                                notify_button.clicked.connect(
                                    lambda checked, c=char_uuid: self.handle_notify_click(client, c))
                                self.current_buttons.append(notify_button)
                                interaction_layout.addWidget(notify_button)
                    
                    # 添加交互按钮到界面
                    self.right_layout.insertLayout(1, interaction_layout)
                    
                    # 保持连接并等待用户操作
                    while client.is_connected:
                        await asyncio.sleep(0.1)
                else:
                    self.detail_text.append("连接失败")
        except Exception as e:
            self.detail_text.append(f"连接错误: {str(e)}")

    def get_characteristic_properties(self, char):
        """获取特征值的属性描述"""
        props = []
        if "read" in char.properties:
            props.append("可读")
        if "write" in char.properties:
            props.append("可写(需响应)")
        if "write-without-response" in char.properties:
            props.append("可写(无响应)")
        if "notify" in char.properties:
            props.append("通知")
        if "indicate" in char.properties:
            props.append("指示")
        return ", ".join(props)

    async def cleanup(self):
        """清理资源"""
        await self.stop_scanning()

    def handle_connect_click(self, address):
        """处理连接按钮点击"""
        loop = asyncio.get_event_loop()
        if loop and loop.is_running():
            loop.create_task(self.connect_to_device(address))

    def handle_scan_click(self):
        """处理扫描按钮点击"""
        loop = asyncio.get_event_loop()
        if loop and loop.is_running():
            loop.create_task(self.toggle_scanning())

    async def handle_characteristic_read(self, client, char_uuid):
        """读取特征值"""
        try:
            value = await client.read_gatt_char(char_uuid)
            self.detail_text.append(f"\n读取值: {value.hex()}")
            # 尝试解析数据
            self.parse_characteristic_value(char_uuid, value)
        except Exception as e:
            self.detail_text.append(f"读取失败: {str(e)}")

    async def handle_characteristic_write(self, client, char_uuid, data, response=True):
        """写入特征值"""
        try:
            if response:
                # 使用需要响应的写入
                await client.write_gatt_char(char_uuid, data)
                self.detail_text.append(f"\n写入成功 (带响应): {data.hex()}")
            else:
                # 使用不需要响应的写入
                await client.write_gatt_char(char_uuid, data, response=False)
                self.detail_text.append(f"\n写入成功 (无响应): {data.hex()}")
        except Exception as e:
            self.detail_text.append(f"写入失败: {str(e)}")
            # 显示更详细的错误信息
            self.detail_text.append(f"错误详情: {type(e).__name__}: {str(e)}")

    def notification_handler(self, sender, data):
        """处理通知数据"""
        self.detail_text.append(f"\n收到通知: {data.hex()}")
        # 尝试解析数据
        self.parse_characteristic_value(sender, data)

    async def handle_characteristic_notify(self, client, char_uuid):
        """订阅特征值通知"""
        try:
            await client.start_notify(char_uuid, self.notification_handler)
            self.detail_text.append(f"\n已订阅通知")
        except Exception as e:
            self.detail_text.append(f"订阅失败: {str(e)}")

    def parse_characteristic_value(self, char_uuid, data):
        """解析特征值数据"""
        try:
            # 根据UUID识别数据类型
            uuid_str = str(char_uuid).upper()
            
            if uuid_str.startswith("2A19"):  # 电池电量
                level = data[0]
                self.detail_text.append(f"电池电量: {level}%")
                
            elif uuid_str.startswith("2A37"):  # 心率测量
                flags = data[0]
                heart_rate = data[1]
                self.detail_text.append(f"心率: {heart_rate} BPM")
                
            elif uuid_str.startswith("2A6E"):  # 温度
                temp = int.from_bytes(data, byteorder='little') / 100
                self.detail_text.append(f"温度: {temp}°C")
                
            elif uuid_str.startswith("2A6F"):  # 湿度
                humidity = int.from_bytes(data, byteorder='little') / 100
                self.detail_text.append(f"湿度: {humidity}%")
                
            else:
                # 尝试将数据解析为ASCII文本
                try:
                    text = data.decode('ascii')
                    self.detail_text.append(f"ASCII文本: {text}")
                except:
                    self.detail_text.append(f"原始数据: {data.hex()}")
        except Exception as e:
            self.detail_text.append(f"解析失败: {str(e)}")

    def handle_read_click(self, client, char_uuid):
        """处理读取按钮点击"""
        loop = asyncio.get_event_loop()
        if loop and loop.is_running():
            loop.create_task(self.handle_characteristic_read(client, char_uuid))

    def handle_write_click(self, client, char_uuid, properties):
        """处理写入按钮点击"""
        # 创建输入对话框
        text, ok = QInputDialog.getText(self, '写入数据', 
                                      '请输入十六进制数据 (例如: 01020304):')
        if ok:
            try:
                data = bytes.fromhex(text)
                loop = asyncio.get_event_loop()
                if loop and loop.is_running():
                    # 根据特征值属性选择写入方式
                    if "write-without-response" in properties:
                        loop.create_task(self.handle_characteristic_write(
                            client, char_uuid, data, response=False))
                    else:
                        loop.create_task(self.handle_characteristic_write(
                            client, char_uuid, data, response=True))
            except ValueError as e:
                QMessageBox.warning(self, "错误", "请输入有效的十六进制数据")
            except Exception as e:
                QMessageBox.warning(self, "错误", f"写入失败: {str(e)}")

    def handle_notify_click(self, client, char_uuid):
        """处理订阅按钮点击"""
        loop = asyncio.get_event_loop()
        if loop and loop.is_running():
            loop.create_task(self.handle_characteristic_notify(client, char_uuid))

    def handle_connection_success(self, address):
        """处理连接成功事件"""
        # 更新设备状态
        if address in self.devices:
            self.devices[address]['connected'] = True
            self.update_device_table()
        
        # 可以在这里添加其他连接成功后的操作
        QMessageBox.information(self, "连接状态", f"设备 {address} 连接成功！")

    def check_device_expiry(self):
        """检查并移除过期设备"""
        current_time = datetime.now()
        expired_devices = []
        
        for address, device in self.devices.items():
            # 如果设备超过30秒没有更新，认为它已离开
            if (current_time - device['last_seen']).total_seconds() > 30:
                expired_devices.append(address)
        
        # 移除过期设备
        for address in expired_devices:
            self.radar_view.remove_device(address)
            del self.devices[address]
        
        # 更新设备表格
        if expired_devices:
            self.update_device_table()

async def main():
    app = QApplication(sys.argv)
    loop = qasync.QEventLoop(app)
    asyncio.set_event_loop(loop)
    
    window = BluetoothGUI()
    window.show()
    
    with loop:
        await loop.run_forever()

if __name__ == '__main__':
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        pass 